{
 "cells": [
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Introduction\n",
    "Currently, the standard procedure within PorePy is to export data to vtu and pvd format for visualization with ParaView. \n",
    "This tutorial explains how to use the `Exporter`. \n",
    "In particular, it will showcase different ways to address data, how constant-in-time data is handled, and how pvd-files are managed. \n",
    "\n",
    "First, an example data set is defined, then the actual exporter is defined, before all supported ways to export data are demonstrated.\n",
    "\n",
    "<b>Note:</b> Related but not necessary for this tutorial: it is highly recommended to read the ParaView documentation. "
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Example contact mechanics model for a mixed-dimensional geometry\n",
    "In order to illustrate the capability and explain the use of the Exporter, we consider a ContactMechanicsBiot model for a two-dimensional fractured geometry. \n",
    "The mixed-dimensional geometry consists of a 2D square and two crossing 1D fractures."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import porepy as pp\n",
    "from porepy.models.derived_models.biot import BiotPoromechanics\n",
    "from porepy.applications.md_grids.model_geometries import (\n",
    "    SquareDomainOrthogonalFractures,\n",
    ")\n",
    "\n",
    "\n",
    "class BiotFractured(SquareDomainOrthogonalFractures, BiotPoromechanics):\n",
    "    def meshing_arguments(self) -> dict:\n",
    "        cell_size = self.solid.convert_units(0.25, \"m\")\n",
    "        return {\"cell_size\": cell_size}\n",
    "\n",
    "\n",
    "params = {\"fracture_indices\": [0, 1]}\n",
    "model = BiotFractured(params)\n",
    "model.prepare_simulation()"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The default data of the model is stored as `pp.TIME_STEP_SOLUTIONS` in the mixed-dimensional grid.\n",
    "Let's have a look:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Keys of the states defined on subdomains: {'u', 't', 'bc_values_mechanics', 'pressure'}\n",
      "Keys of the states defined on interfaces: {'interface_darcy_flux', 'u_interface'}\n"
     ]
    }
   ],
   "source": [
    "# Determine all keys of all states on all subdomains\n",
    "subdomain_states = []\n",
    "for sd, data in model.mdg.subdomains(return_data=True):\n",
    "    subdomain_states += data[pp.TIME_STEP_SOLUTIONS].keys()\n",
    "print(\"Keys of the states defined on subdomains:\", set(subdomain_states))\n",
    "\n",
    "# Determine all keys of all states on all interfaces\n",
    "interface_states = []\n",
    "for sd, data in model.mdg.interfaces(return_data=True):\n",
    "    interface_states += data[pp.TIME_STEP_SOLUTIONS].keys()\n",
    "print(\"Keys of the states defined on interfaces:\", set(interface_states))"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Defining the exporter\n",
    "Two arguments are required to define an object of type pp.Exporter: a mixed-dimensional grid, and the target name of the output. \n",
    "Optionally, one can add a directory name, and instead of a mixed-dimensional grid, single grids can also be provided (see Example 7)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [],
   "source": [
    "exporter = pp.Exporter(model.mdg, file_name=\"file\", folder_name=\"folder\")"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In the following, we showcase how to use the main subroutines for exporting data:\n",
    "- write_vtu()\n",
    "- write_pvd()\n",
    "\n",
    "The former addresses the export of data for a specific time step, while the latter gathers the previous exports and collects them in a single file. \n",
    "This allows an easier analysis in ParaView."
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Example 1: Exporting states\n",
    "Data stored in the mixed-dimensional grid under `pp.TIME_STEP_SOLUTIONS` can be simply exported by addressing their keys using the routine `write_vtu()`. \n",
    "We define a dedicated exporter for this task."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [],
   "source": [
    "exporter_1 = pp.Exporter(model.mdg, file_name=\"example-1\",folder_name=\"exporter-tutorial\") \n",
    "exporter_1.write_vtu([\n",
    "    model.pressure_variable,\n",
    "    model.displacement_variable,\n",
    "    model.interface_darcy_flux_variable\n",
    "    ])"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<b>Note:</b> Here all available representations (i.e., on all dimensions) of the states will be exported."
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Example 2: Exporting states on specified grids\n",
    "Similar to Example 1, we will again export states by addressing their keys, but target only a subset of grids. \n",
    "For instance, we fetch the grids for the subdomains and interface.\n",
    "\n",
    "<b>Note:</b> For now, one has to make sure that subsets of the mixed-dimensional grid contain all grids of a particular dimension."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [],
   "source": [
    "subdomains_1d = model.mdg.subdomains(dim=1)\n",
    "subdomains_2d = model.mdg.subdomains(dim=2)\n",
    "interfaces_1d = model.mdg.interfaces(dim=1)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And as a simple example extract the 2D subdomain:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [],
   "source": [
    "sd_2d = subdomains_2d[0]"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We export pressure on all 1D subdomains, displacements on all 2D subdomains, and the mortar pressures on all interfaces. \n",
    "For this, we use tuples of grid(s) and keys. \n",
    "In order to not overwrite the previous data, we define a new exporter."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [],
   "source": [
    "exporter_2 = pp.Exporter(model.mdg, \"example-2\", \"exporter-tutorial\")\n",
    "exporter_2.write_vtu([\n",
    "    (subdomains_1d, model.pressure_variable), \n",
    "    (subdomains_2d, model.displacement_variable),\n",
    "    (interfaces_1d, model.interface_darcy_flux_variable),\n",
    "    ])"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {
    "tags": []
   },
   "source": [
    "## Example 3: Exporting explicitly defined data\n",
    "We can also export data which is not stored in the mixed-dimensional grid under `pp.TIME_STEP_SOLUTIONS`. \n",
    "This capability requires defining tuples of (1) a single grid, (2) a key, and (3) the data vector. \n",
    "For example, let's export the cell centers of the 2D subdomain 'sd_2d', as well as all interfaces (with different signs for the sake of the example). \n",
    "Again, we define a dedicated exporter for this task.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "subdomain_data = [(sd_2d, \"cc\", sd_2d.cell_centers)]\n",
    "interface_data = [\n",
    "    (intf, \"cc_e\", (-1) ** i * intf.cell_centers)\n",
    "    for i, intf in enumerate(interfaces_1d)\n",
    "]\n",
    "exporter_3 = pp.Exporter(model.mdg, \"example-3\", \"exporter-tutorial\")\n",
    "exporter_3.write_vtu(subdomain_data + interface_data)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Example 4: Flexibility in the input arguments\n",
    "The export allows for an arbitrary combination of all previous ways to export data.\n",
    "\n",
    "Specifically, this example shows how to do the following export combination:\n",
    "* The \"custom\" data vector on the `sd_2d` from example 3\n",
    "* The pressure variable only for `subdomain_1d`\n",
    "* All available representations of the displacement\n",
    "* The \"custom\" interface data from example 3"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [],
   "source": [
    "exporter_4 = pp.Exporter(model.mdg, \"example-4\", \"exporter-tutorial\")\n",
    "exporter_4.write_vtu(\n",
    "    [\n",
    "        (sd_2d, \"cc\", sd_2d.cell_centers),\n",
    "        (subdomains_1d, model.pressure_variable), \n",
    "        model.displacement_variable] + interface_data\n",
    "    )"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Example 5: Exporting data in a time series\n",
    "Data can also be exported in a time series, and the Exporter takes care of managing the file names. \n",
    "The user will only have to prescribe the time step number, and here we consider a time series consisting of 5 steps. \n",
    "For simplicity, we look at an analogous situation as in Example 1."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [],
   "source": [
    "exporter_5 = pp.Exporter(model.mdg, \"example-5\", \"exporter-tutorial\")\n",
    "variable_names = [\n",
    "    model.pressure_variable,\n",
    "    model.displacement_variable,\n",
    "    model.interface_darcy_flux_variable,\n",
    "    ]\n",
    "for step in range(5):\n",
    "    # Data may change\n",
    "    exporter_5.write_vtu(variable_names, time_step=step)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Alternatively, one can also let the Exporter internally manage the stepping and the appendix used when storing the data to file. \n",
    "This is triggered by the keyword `time_dependent`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [],
   "source": [
    "exporter_5 = pp.Exporter(model.mdg, \"example-5\", \"exporter-tutorial\")\n",
    "for step in range(5):\n",
    "    # Data may change\n",
    "    exporter_5.write_vtu(variable_names, time_dependent=True)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Example 6: Exporting constant data\n",
    "The export of both grid and geometry related data as well as heterogeneous material parameters may be of interest. \n",
    "However, these often change very seldomly in time or are even constant in time. \n",
    "In order to save storage space, constant data is stored separately. \n",
    "A multirate approach is used to address slowly changing \"constant\" data, which results in an extra set of output files. \n",
    "Every time constant data has to be updated (in a time series), the output files are updated as well. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [],
   "source": [
    "exporter_6_a = pp.Exporter(model.mdg, \"example-6-a\", \"exporter-tutorial\")\n",
    "# Define some \"constant\" data\n",
    "exporter_6_a.add_constant_data([(sd_2d, \"cc\", sd_2d.cell_centers)])\n",
    "for step in range(5):\n",
    "    # Update the previously defined \"constant\" data\n",
    "    if step == 2:\n",
    "        exporter_6_a.add_constant_data([(sd_2d, \"cc\", -sd_2d.cell_centers)])\n",
    "    # All constant data will be exported also if not specified\n",
    "    exporter_6_a.write_vtu(variable_names, time_step=step)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The default is that constant data is always printed to extra files. \n",
    "Since the vtu format requires geometrical and topological information on the mesh (points, connectivity etc.), this type of constant data is exported to each vtu file. \n",
    "Depending on the situation, this overhead can be significant. \n",
    "Thus, one can also choose to print the constant data to the same files as the standard data, by setting a keyword when defining the exporter. \n",
    "With a similar setup as in part A (just above), the same output is generated, but managed differently among files."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [],
   "source": [
    "exporter_6_b = pp.Exporter(model.mdg, \"example-6-b\", \"exporter-tutorial\", export_constants_separately = False)\n",
    "exporter_6_b.add_constant_data([(sd_2d, \"cc\", sd_2d.cell_centers)])\n",
    "for step in range(5):\n",
    "    # Update the previously defined \"constant\" data\n",
    "    if step == 2:\n",
    "        exporter_6_b.add_constant_data([(sd_2d, \"cc\", -sd_2d.cell_centers)])\n",
    "    # All constant data will be exported also if not specified\n",
    "    exporter_6_b.write_vtu(variable_names, time_step=step)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Example 5 revisisted: PVD format\n",
    "The pvd format collects previously exported data. \n",
    "At every application of `write_vtu` a corresponding pvd file is generated. \n",
    "This file gathers all \"vtu\" files correpsonding to this time step. It is recommended to use the \"pvd\" file for analyzing the data in ParaView.\n",
    "\n",
    "In addition, when considering a time series, it is possible to gather data connected to multiple time steps and assign the actual time to each time step. \n",
    "Assume that Example 5 corresponds to an adaptive time stepping. \n",
    "We define the actual times, and collect the exported data from Example 5 in a single pvd file."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [],
   "source": [
    "times_5 = [0.0, 0.1, 1.0, 2.0, 10.0]\n",
    "exporter_5.write_pvd(times_5)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "When providing no argument to `write_pvd()`, the time steps are used as actual times."
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Example 7: Exporting data on a single grid\n",
    "It is also possible to export data without prescribing a mixed-dimensional grid, but a single grid. In this case, one has to assign the data when writing to vtu. For this, a key and a data array (with suitable size) have to be provided."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {},
   "outputs": [],
   "source": [
    "exporter_7 = pp.Exporter(sd_2d, \"example-7\", \"exporter-tutorial\")\n",
    "exporter_7.write_vtu([(\"cc\", sd_2d.cell_centers)])"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# What we have explored\n",
    "Both mixed-dimensional and single grids in PorePy, in addition to their related information such as solutions and parameters, can be exported to the vtu format.\n",
    "Further they can be visualized in ParaView (or other compatible software). \n",
    "The key object is `pp.Exporter` and its `write_vtu` method, which allow for several modes of exporting information."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "condapp",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.4"
  },
  "vscode": {
   "interpreter": {
    "hash": "ffc5b0a7f9c7db892dae2ef51b4b23072576b4ba6e83a9602165851401d5c9eb"
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
